#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#define NUM_CLIENTS 2     //max number of clients we intend to serve
#define MAX_MSG_SIZE 256

char ExitFlag = 0;     //a global flag to indicate threads to KILL themselves

struct echo_thread_args{
	int fd;
	char *ExitFlag;
};

void *echo_serve_client(void *args);
int sendall(int s, char *buf, int *len);

int main(int argc, char **argv)
{

	int listener;     // listening socket descriptor
	int newfd;        // newly accept()ed socket descriptor
	char *PORT;			/*DEBUG we might need to check input - this var may be < 0*/
	struct sockaddr_in remoteaddr; // client address
	socklen_t addrlen;

	fd_set master, readfds;

	int yes=1;        // for setsockopt() SO_REUSEADDR, below
	int rv;

	struct addrinfo hints, *ai;

	//pthread_t threads[NUM_CLIENTS];
	pthread_t thread;
	int thread_rv;
	struct echo_thread_args eta[1];

	if (argc != 2){
		fprintf(stdout, "ERROR: please enter port number as an argument\n");
		return 0;
	}

	PORT = argv[1];

	// get us a socket and bind it

	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_flags = AI_PASSIVE;
	if ((rv = getaddrinfo("127.0.0.1", PORT, &hints, &ai)) != 0) {
		fprintf(stdout, "ERROR: %s\n", gai_strerror(rv));   //rv is just for error ref.
		return 0;
	}

	listener = socket(ai->ai_family, ai->ai_socktype, 0);
	if (listener < 0) {
		fprintf(stdout, "ERROR: couldn't get a socket from the OS!!\n");
		freeaddrinfo(ai); // all done with this
		return 0;
	}
	fprintf(stdout,"fd = %d\n", listener);				/*DEBUG*/

	// lose the pesky "address already in use" error message   --- this is another one of beej's functions
	setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

	if (bind(listener, ai->ai_addr, ai->ai_addrlen) < 0) {
		close(listener);
		freeaddrinfo(ai); // all done with this
		return 0;
	}

	freeaddrinfo(ai); // all done with this

	// listen
	if (listen(listener, 15) == -1) {
		perror("listen");
		close(listener);
		return 0;
	}

	FD_ZERO(master);
	fd_ZERO(readfds);

	FD_SET(listener, &master);	
	
	// main loop
	while(1) {
		 read_fds = master; // copy it
       		 if (select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1) {
          		perror("select");
            		exit(4);
        	}
		

		newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
		fprintf(stdout, "new fd is: %d\n", newfd);
		eta[0].fd = newfd;
		thread_rv = pthread_create(&thread, NULL, (void *)echo_serve_client, (void *)&eta[0]);
		if (thread_rv){
			fprintf(stderr, "ERROR; return code from pthread_create() is %d\n", thread_rv);
		}
	}

	close(listener);
	return 0;
}









int sendall(int s, char *buf, int *len);

void *echo_serve_client(void *args);


/*beej's function: wrapping a send() function to make sure big buffers
 * are sent properly
 */
int sendall(int s, char *buf, int *len)
{
	int total = 0;        // how many bytes we've sent
	int bytesleft = *len; // how many we have left to send
	int n;

	while(total < *len) {
		n = send(s, buf+total, bytesleft, 0);
		if (n == -1) { break; }
		total += n;
		bytesleft -= n;
	}

	*len = total; // return number actually sent here

	return n==-1?-1:0; // return -1 on failure, 0 on success
}

/*the function we pass to a thread on new connections in the echo_client program
 *
 */
void *echo_serve_client(void *args){
	int numbytes = -2, fd;
	char buf[MAX_MSG_SIZE];
	char *ExitFlag;
	struct echo_thread_args *eta;

	eta = (struct echo_thread_args *)args;
	fd = eta->fd;
	ExitFlag = eta->ExitFlag;

	fprintf(stdout, "entered with fd: %d\n", fd);

	while (!(*ExitFlag)){
		numbytes = recv(fd, buf, MAX_MSG_SIZE ,0);
		fprintf(stdout, "DEBUG: %d\n", numbytes);     /*DEBUG*/
		if (numbytes == 0){    //connection closed
			break;
		}

		if (numbytes == -1){
			fprintf(stdout, "error handling client\n");
			break;
		}

		buf[numbytes] = '\0';
		fprintf(stdout, "buf in thread is == %s\n", buf);				/*DEBUG*/
		if (strcmp(buf, "exit") == 0){
			break;
		}

		if (sendall(fd, buf, &numbytes) == -1){
			fprintf(stderr, "ERROR: failed to send the whole buffer\n"
					"sent only %d bytes out of %d, ", numbytes, (int)strlen(buf));
		}
	}

	close(fd);
	fprintf(stdout, "exiting thread\n");  			/*DEBUG*/
	pthread_exit(NULL);

}


